/*
 * Freesclae MXS Touchscreen driver
 *
 * Author: Vitaly Wool <vital@embeddedalley.com>
 *
 * Copyright 2008-2010 Freescale Semiconductor, Inc.
 * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved.
 */

/*
 * The code contained herein is licensed under the GNU General Public
 * License. You may obtain a copy of the GNU General Public License
 * Version 2 or later at the following locations:
 *
 * http://www.opensource.org/licenses/gpl-license.html
 * http://www.gnu.org/copyleft/gpl.html
 */


/*
 * Fix 长按屏幕，尤其是中断函数里有大块printk，将导致UI响应很慢，假死，无法关机，包括：
 *      - 长按关机键7秒
 *      - 连续点击关机键20次、
 *      - 长按关机键2秒UI点击关机后屏幕灭掉，但电源LED没有灭，完全死机
 * 采用中断下半部在 work_struct 里执行，降低触屏中断优先级
 */

#include <linux/slab.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/pm.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/fsl_devices.h>

#include <mach/hardware.h>
#include <mach/lradc.h>
#include <mach/device.h>
#include <mach/regs-lradc.h>

#include <linux/delay.h>
#include <linux/input-misc.h>

#define TOUCH_DEBOUNCE_TOLERANCE	100

extern int bl_power_on(void);
//extern int is_ignore_input();

struct mxs_ts_info {
    // Fix 降低中断优先级
    struct work_struct work;
    int irq_type;
    // end Fix
    
	int touch_irq;
	int device_irq;
	unsigned int base;
	u8 x_plus_chan;
	u8 x_minus_chan;
	u8 y_plus_chan;
	u8 y_minus_chan;

	unsigned int x_plus_val;
	unsigned int x_minus_val;
	unsigned int y_plus_val;
	unsigned int y_minus_val;
	unsigned int x_plus_mask;
	unsigned int x_minus_mask;
	unsigned int y_plus_mask;
	unsigned int y_minus_mask;

	struct input_dev *idev;
	enum {
		TS_STATE_DISABLED,
		TS_STATE_TOUCH_DETECT,
		TS_STATE_TOUCH_VERIFY,
		TS_STATE_X_PLANE,
		TS_STATE_Y_PLANE,
	} state;
	u16 x;
	u16 y;
	int sample_count;
	int ads[8];
};

static inline void enter_state_touch_detect(struct mxs_ts_info *info)
{
	__raw_writel(0xFFFFFFFF,
	             info->base + HW_LRADC_CHn_CLR(info->x_plus_chan));
	__raw_writel(0xFFFFFFFF,
	             info->base + HW_LRADC_CHn_CLR(info->y_plus_chan));
	__raw_writel(0xFFFFFFFF,
	             info->base + HW_LRADC_CHn_CLR(info->x_minus_chan));
	__raw_writel(0xFFFFFFFF,
	             info->base + HW_LRADC_CHn_CLR(info->y_minus_chan));

	__raw_writel(BM_LRADC_CTRL1_LRADC0_IRQ << info->y_minus_chan,
	             info->base + HW_LRADC_CTRL1_CLR);
	__raw_writel(BM_LRADC_CTRL1_TOUCH_DETECT_IRQ,
	             info->base + HW_LRADC_CTRL1_CLR);
	/*
	 * turn off the yplus and yminus pullup and pulldown, and turn off touch
	 * detect (enables yminus, and xplus through a resistor.On a press,
	 * xplus is pulled down)
	 */
	__raw_writel(info->y_plus_mask, info->base + HW_LRADC_CTRL0_CLR);
	__raw_writel(info->y_minus_mask, info->base + HW_LRADC_CTRL0_CLR);
	__raw_writel(info->x_plus_mask, info->base + HW_LRADC_CTRL0_CLR);
	__raw_writel(info->x_minus_mask, info->base + HW_LRADC_CTRL0_CLR);
	__raw_writel(BM_LRADC_CTRL0_TOUCH_DETECT_ENABLE,//touch detect enable
	             info->base + HW_LRADC_CTRL0_SET);
	hw_lradc_set_delay_trigger_kick(LRADC_DELAY_TRIGGER_TOUCHSCREEN, 0);
	info->state = TS_STATE_TOUCH_DETECT;
	info->sample_count = 0;
}

static inline void enter_state_disabled(struct mxs_ts_info *info)
{
	__raw_writel(info->y_plus_mask, info->base + HW_LRADC_CTRL0_CLR);
	__raw_writel(info->y_minus_mask, info->base + HW_LRADC_CTRL0_CLR);
	__raw_writel(info->x_plus_mask, info->base + HW_LRADC_CTRL0_CLR);
	__raw_writel(info->x_minus_mask, info->base + HW_LRADC_CTRL0_CLR);
	__raw_writel(BM_LRADC_CTRL0_TOUCH_DETECT_ENABLE,
	             info->base + HW_LRADC_CTRL0_CLR);
	hw_lradc_set_delay_trigger_kick(LRADC_DELAY_TRIGGER_TOUCHSCREEN, 0);
	info->state = TS_STATE_DISABLED;
	info->sample_count = 0;
}


static inline void enter_state_x_plane(struct mxs_ts_info *info)
{
	__raw_writel(info->y_plus_val, info->base + HW_LRADC_CTRL0_SET);
	__raw_writel(info->y_minus_val, info->base + HW_LRADC_CTRL0_SET);
	__raw_writel(info->x_plus_mask, info->base + HW_LRADC_CTRL0_CLR);
	__raw_writel(info->x_minus_mask, info->base + HW_LRADC_CTRL0_CLR);
	__raw_writel(BM_LRADC_CTRL0_TOUCH_DETECT_ENABLE,
	             info->base + HW_LRADC_CTRL0_CLR);
	__raw_writel(BF_LRADC_CHn_NUM_SAMPLES(1),
	             info->base + HW_LRADC_CHn(info->x_plus_chan));
	hw_lradc_set_delay_trigger_kick(LRADC_DELAY_TRIGGER_TOUCHSCREEN, 1);

	info->state = TS_STATE_X_PLANE;
	info->sample_count = 0;
}

static inline void enter_state_y_plane(struct mxs_ts_info *info)
{
	__raw_writel(info->y_plus_mask, info->base + HW_LRADC_CTRL0_CLR);
	__raw_writel(info->y_minus_mask, info->base + HW_LRADC_CTRL0_CLR);
	__raw_writel(info->x_plus_val, info->base + HW_LRADC_CTRL0_SET);
	__raw_writel(info->x_minus_val, info->base + HW_LRADC_CTRL0_SET);
	__raw_writel(BM_LRADC_CTRL0_TOUCH_DETECT_ENABLE,
	             info->base + HW_LRADC_CTRL0_CLR);
	__raw_writel(BF_LRADC_CHn_NUM_SAMPLES(1),
	             info->base + HW_LRADC_CHn(info->y_plus_chan));
	hw_lradc_set_delay_trigger_kick(LRADC_DELAY_TRIGGER_TOUCHSCREEN, 1);
	info->state = TS_STATE_Y_PLANE;
	info->sample_count = 0;
}

static inline void enter_state_touch_verify(struct mxs_ts_info *info)
{
	__raw_writel(info->y_plus_mask, info->base + HW_LRADC_CTRL0_CLR);
	__raw_writel(info->y_minus_mask, info->base + HW_LRADC_CTRL0_CLR);
	__raw_writel(info->x_plus_mask, info->base + HW_LRADC_CTRL0_CLR);
	__raw_writel(info->x_minus_mask, info->base + HW_LRADC_CTRL0_CLR);
	__raw_writel(BM_LRADC_CTRL0_TOUCH_DETECT_ENABLE,
	             info->base + HW_LRADC_CTRL0_SET);
	info->state = TS_STATE_TOUCH_VERIFY;
	hw_lradc_set_delay_trigger_kick(LRADC_DELAY_TRIGGER_TOUCHSCREEN, 1);
	info->sample_count = 0;
}
static void process_lradc(struct mxs_ts_info *info, u16 x, u16 y,
                          int pressure)
{
	static int last_x = 0, last_y = 10;
	switch (info->state) {
	case TS_STATE_X_PLANE:
		info->ads[info->sample_count] = x;
		info->sample_count++;
#if 0
		if (info->sample_count >= 3) {
			info->x = (info->ads[0] + info->ads[1] + info->ads[2] + info->ads[1]) >> 2;
			info->sample_count = 0;
			if (
			    //	abs(info->ads[0] - info->ads[1]) >  60 ||
			    //    abs(info->ads[0] - info->ads[2]) >  60 ||
			    abs(info->ads[0] - info->ads[2]) >  60 ) {
				hw_lradc_set_delay_trigger_kick(LRADC_DELAY_TRIGGER_TOUCHSCREEN, 1);
			} else {
				enter_state_touch_verify(info);
			}

		} else {
			hw_lradc_set_delay_trigger_kick(LRADC_DELAY_TRIGGER_TOUCHSCREEN, 1);
		}
#else
		if (1 || info->sample_count >= 1) {
			info->x = (info->ads[0]);
			info->sample_count = 0;
			enter_state_touch_verify(info);

		} else {
		}

#endif
		break;
	case TS_STATE_Y_PLANE:
		info->ads[info->sample_count] = y;
		info->sample_count++;
#if 0
		if (info->sample_count >= 3) {
			info->y = (info->ads[0] + info->ads[1] + info->ads[2] + info->ads[0]) >> 2;
			info->sample_count = 0;
			if (
			    //		abs(info->ads[0] - info->ads[1]) >  60 ||
			    //	    abs(info->ads[0] - info->ads[2]) >  60 ||
			    abs(info->ads[0] - info->ads[2]) >  60) {
				hw_lradc_set_delay_trigger_kick(LRADC_DELAY_TRIGGER_TOUCHSCREEN, 1);
			} else {
				enter_state_x_plane(info);
			}

		} else {
			hw_lradc_set_delay_trigger_kick(LRADC_DELAY_TRIGGER_TOUCHSCREEN, 1);
		}
#else
		if (1 || info->sample_count >= 1) {
			info->y = (info->ads[0]);
			info->sample_count = 0;
			enter_state_x_plane(info);

		} else {
		}

#endif
		break;
	case TS_STATE_TOUCH_VERIFY: {

		if (last_x == info->x) {
			info->x++;
		}
		if (last_y == info->y) {
			info->y++;
		}
#if 0
		if (abs(last_x - info->x) < 100)  {
			//input_report_abs(info->idev, ABS_Y, info->y);
			//input_report_abs(info->idev, ABS_X, info->x);
			//input_report_abs(info->idev, ABS_PRESSURE, pressure);
			//input_sync(info->idev);
		}
		last_x = info->x;
		last_y = info->y;
#endif

	}
	/* fall through */
	case TS_STATE_TOUCH_DETECT:
		pr_debug("%s: touch detect state, sample_count %d\n", __func__,
		         info->sample_count);
		if (pressure) {
			// 这两行不能注释，否则一次按下变成两次按下，原来的程序有BUG，
			// pressure不一定是真的按下
			//input_report_abs(info->idev, ABS_PRESSURE, pressure);
			//input_sync(info->idev);

			if (abs(last_x - info->x) < 100 && abs(last_y - info->y) < 100) {
				if (!is_ignore_input()) {
					input_report_abs(info->idev, ABS_PRESSURE, 1);
					input_report_abs(info->idev, ABS_Y, info->y);
					input_report_abs(info->idev, ABS_X, info->x);
					input_sync(info->idev);
				}
			}
			last_x = info->x;
			last_y = info->y;
			enter_state_y_plane(info);
			hw_lradc_set_delay_trigger_kick(
					LRADC_DELAY_TRIGGER_TOUCHSCREEN, 1);
		} else {
			if (!is_ignore_input()) {
				input_report_abs(info->idev, ABS_PRESSURE, 0);
				input_sync(info->idev);
			}
#if defined(CONFIG_BL_KEY)
			bl_power_on();
#endif
			enter_state_touch_detect(info);
		}
		break;

	default:
		printk(KERN_ERR "%s: unknown touchscreen state %d\n", __func__,
		       info->state);
	}
}

static irqreturn_t ts_handler(int irq, void *dev_id)
{

    struct mxs_ts_info *info = (struct mxs_ts_info *)dev_id;

    info->irq_type = irq;
    /* 关闭几个中断
    * 如果不调用schedule_work  那么  **每按下一次产生一次中断**
    * Fix 下面的printk如果打开，将可能导致系统响应太慢，仅用于调试
    */
    // 这个探测中断时基础，有没有 schedule_work 都会执行
    if (info->irq_type == info->touch_irq) {
		__raw_writel(BM_LRADC_CTRL1_TOUCH_DETECT_IRQ,
		             info->base + HW_LRADC_CTRL1_CLR);
        // printk("irq detect\r\n");
	}
    // 这个中断需要在 schedule_work 的 process_lradc 里启动读取 Y 坐标
	else if (info->irq_type == info->device_irq) {
		__raw_writel(BM_LRADC_CTRL1_LRADC0_IRQ << info->y_minus_chan,
		             info->base + HW_LRADC_CTRL1_CLR);
        // printk("irq adc_Y\r\n");     // 这一行在屏蔽 schedule_work 是看不到的
	}
    schedule_work(&info->work);  // Fix 降低中断优先级
    return IRQ_HANDLED;
	
}

static void gpio_keys_work_func(struct work_struct *work)
{
    // printk("work里执行读取寄存器操作，如果采用中断方式，这些打印将使得系统卡死\r\n");

	struct mxs_ts_info *info =
	    container_of(work, struct mxs_ts_info, work);
    

	u16 x_plus, y_plus;
	int pressure = 0;

	x_plus = __raw_readl(info->base + HW_LRADC_CHn(info->x_plus_chan)) &
	         BM_LRADC_CHn_VALUE;
	y_plus = __raw_readl(info->base + HW_LRADC_CHn(info->y_plus_chan)) &
	         BM_LRADC_CHn_VALUE;

	/* pressed? */
	if (__raw_readl(info->base + HW_LRADC_STATUS) &
	    BM_LRADC_STATUS_TOUCH_DETECT_RAW) {
		pressure = 1;
	}

	pr_debug("%s: irq %d, x_plus %d, y_plus %d, pressure %d\n",
	         __func__, info->irq_type, x_plus, y_plus, pressure);

	process_lradc(info, x_plus, y_plus, pressure);//y_plus is x zuobiao , x_plus is y zuobiao
}

static int __devinit mxs_ts_probe(struct platform_device *pdev)
{
	struct input_dev *idev;
	struct mxs_ts_info *info;
	int ret = 0;
	struct resource *res;
	struct mxs_touchscreen_plat_data *plat_data;

	plat_data = (struct mxs_touchscreen_plat_data *)pdev->dev.platform_data;
	if (plat_data == NULL) {
		return -ENODEV;
	}

	idev = input_allocate_device();
	if (idev == NULL) {
		return -ENOMEM;
	}

	info = kzalloc(sizeof(struct mxs_ts_info), GFP_KERNEL);
	if (info == NULL) {
		ret = -ENOMEM;
		goto out_nomem_info;
	}
    INIT_WORK(&info->work, gpio_keys_work_func);

	idev->name = "MXS touchscreen";
	//idev->evbit[0] = BIT(EV_ABS);
	idev->evbit[0] = BIT(EV_ABS) | BIT_MASK(EV_KEY);		// modefy by zhuguojun
	input_set_abs_params(idev, ABS_X, 0, 0xFFF, 0, 0);
	input_set_abs_params(idev, ABS_Y, 0, 0xFFF, 0, 0);
	input_set_abs_params(idev, ABS_PRESSURE, 0, 1, 0, 0);

	ret = input_register_device(idev);
	if (ret) {
		goto out_nomem;
	}

	info->idev = idev;
	info->x_plus_chan = plat_data->x_plus_chan;
	info->x_minus_chan = plat_data->x_minus_chan;
	info->y_plus_chan = plat_data->y_plus_chan;
	info->y_minus_chan = plat_data->y_minus_chan;
	info->x_plus_val = plat_data->x_plus_val;
	info->x_minus_val = plat_data->x_minus_val;
	info->y_plus_val = plat_data->y_plus_val;
	info->y_minus_val = plat_data->y_minus_val;
	info->x_plus_mask = plat_data->x_plus_mask;
	info->x_minus_mask = plat_data->x_minus_mask;
	info->y_plus_mask = plat_data->y_plus_mask;
	info->y_minus_mask = plat_data->y_minus_mask;

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (!res) {
		printk(KERN_ERR "%s: couldn't get MEM resource\n", __func__);
		ret = -ENODEV;
		goto out_nodev;
	}
	info->base = (unsigned int)IO_ADDRESS(res->start);
	res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
	if (!res) {
		printk(KERN_ERR "%s: couldn't get IRQ resource\n", __func__);
		ret = -ENODEV;
		goto out_nodev;
	}
	info->touch_irq = res->start;

	res = platform_get_resource(pdev, IORESOURCE_IRQ, 1);
	if (!res) {
		printk(KERN_ERR "%s: couldn't get IRQ resource\n", __func__);
		ret = -ENODEV;
		goto out_nodev;
	}
	info->device_irq = res->start;

	ret = request_irq(info->touch_irq, ts_handler, IRQF_DISABLED,
	                  "mxs_ts_touch", info);
	if (ret) {
		goto out_nodev;
	}

	ret = request_irq(info->device_irq, ts_handler, IRQF_DISABLED,
	                  "mxs_ts_dev", info);
	if (ret) {
		free_irq(info->touch_irq, info);
		goto out_nodev;
	}

	enter_state_touch_detect(info);

	hw_lradc_use_channel(info->x_plus_chan);
	hw_lradc_use_channel(info->x_minus_chan);
	hw_lradc_use_channel(info->y_plus_chan);
	hw_lradc_use_channel(info->y_minus_chan);
	hw_lradc_configure_channel(info->x_plus_chan, 0, 0, 0);
	hw_lradc_configure_channel(info->x_minus_chan, 0, 0, 0);
	hw_lradc_configure_channel(info->y_plus_chan, 0, 0, 0);
	hw_lradc_configure_channel(info->y_minus_chan, 0, 0, 0);

	/* Clear the accumulator & NUM_SAMPLES for the channels */
	__raw_writel(0xFFFFFFFF,
	             info->base + HW_LRADC_CHn_CLR(info->x_plus_chan));
	__raw_writel(0xFFFFFFFF,
	             info->base + HW_LRADC_CHn_CLR(info->x_minus_chan));
	__raw_writel(0xFFFFFFFF,
	             info->base + HW_LRADC_CHn_CLR(info->y_plus_chan));
	__raw_writel(0xFFFFFFFF,
	             info->base + HW_LRADC_CHn_CLR(info->y_minus_chan));

	hw_lradc_set_delay_trigger(LRADC_DELAY_TRIGGER_TOUCHSCREEN,
	                           0x3c, 0, 0, 10); //8/2 ms trigger

	__raw_writel(BM_LRADC_CTRL1_LRADC0_IRQ << info->y_minus_chan,
	             info->base + HW_LRADC_CTRL1_CLR); // clear ch5  iqr pending bit
	__raw_writel(BM_LRADC_CTRL1_TOUCH_DETECT_IRQ,  // clear touch detect  iqr pending bit
	             info->base + HW_LRADC_CTRL1_CLR);

	__raw_writel(BM_LRADC_CTRL1_LRADC0_IRQ_EN << info->y_minus_chan,
	             info->base + HW_LRADC_CTRL1_SET);  //ch5 irq enable
	__raw_writel(BM_LRADC_CTRL1_TOUCH_DETECT_IRQ_EN,//touch detect irq enbale
	             info->base + HW_LRADC_CTRL1_SET);

	platform_set_drvdata(pdev, info);
	device_init_wakeup(&pdev->dev, 1);
	goto out;

out_nodev:
	input_free_device(idev);
out_nomem:
	kfree(info);
out_nomem_info:
	kfree(idev);
out:
	return ret;
}

static int __devexit mxs_ts_remove(struct platform_device *pdev)
{
	struct mxs_ts_info *info = platform_get_drvdata(pdev);

	platform_set_drvdata(pdev, NULL);

	hw_lradc_unuse_channel(info->x_plus_chan);
	hw_lradc_unuse_channel(info->x_minus_chan);
	hw_lradc_unuse_channel(info->y_plus_chan);
	hw_lradc_unuse_channel(info->y_minus_chan);

	__raw_writel(BM_LRADC_CTRL1_LRADC0_IRQ_EN << info->y_minus_chan,
	             info->base + HW_LRADC_CTRL1_CLR);
	__raw_writel(BM_LRADC_CTRL1_TOUCH_DETECT_IRQ_EN,
	             info->base + HW_LRADC_CTRL1_CLR);

	free_irq(info->device_irq, info);
	free_irq(info->touch_irq, info);
	input_free_device(info->idev);

	enter_state_disabled(info);
	kfree(info->idev);
	kfree(info);
	return 0;
}

#ifdef CONFIG_PM
static int mxs_ts_suspend(struct platform_device *pdev,
                          pm_message_t state)
{
	struct mxs_ts_info *info = platform_get_drvdata(pdev);

	if (!device_may_wakeup(&pdev->dev)) {
		hw_lradc_unuse_channel(info->x_plus_chan);
		hw_lradc_unuse_channel(info->x_minus_chan);
		hw_lradc_unuse_channel(info->y_plus_chan);
		hw_lradc_unuse_channel(info->y_minus_chan);
	}
	return 0;
}

static int mxs_ts_resume(struct platform_device *pdev)
{
	struct mxs_ts_info *info = platform_get_drvdata(pdev);

	if (!device_may_wakeup(&pdev->dev)) {
		hw_lradc_use_channel(info->x_plus_chan);
		hw_lradc_use_channel(info->x_minus_chan);
		hw_lradc_use_channel(info->y_plus_chan);
		hw_lradc_use_channel(info->y_minus_chan);
	}
	return 0;
}
#endif

static struct platform_driver mxs_ts_driver = {
	.probe		= mxs_ts_probe,
	.remove		= __devexit_p(mxs_ts_remove),
#ifdef CONFIG_PM
	.suspend	= mxs_ts_suspend,
	.resume		= mxs_ts_resume,
#endif
	.driver		= {
		.name	= "mxs-ts",
	},
};

static int __init mxs_ts_init(void)
{
	return platform_driver_register(&mxs_ts_driver);
}

static void __exit mxs_ts_exit(void)
{
	platform_driver_unregister(&mxs_ts_driver);
}

module_init(mxs_ts_init);
module_exit(mxs_ts_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("glink devices user space export");